#include "helper.h"
#include "monitor.h"
#include "reg.h"
#include "memory.h"

extern uint32_t instr;
extern char assembly[80];

/* decode I-type instrucion with unsigned immediate */
static void decode_imm_type(uint32_t instr) {

	op_src1->type = OP_TYPE_REG;//I型指令有1源寄存器，1目的寄存器，1个立即数
	op_src1->reg = (instr & RS_MASK) >> (RT_SIZE + IMM_SIZE);
	op_src1->val = reg_w(op_src1->reg);//Get rs reg's code
	
	op_src2->type = OP_TYPE_IMM;
	op_src2->imm = instr & IMM_MASK;//Get imm
	op_src2->val = op_src2->imm;

	op_dest->type = OP_TYPE_REG;
	op_dest->reg = (instr & RT_MASK) >> (IMM_SIZE);//Get rt reg's code
}

make_helper(lui) {

	decode_imm_type(instr);
	reg_w(op_dest->reg) = (op_src2->val << 16);
	sprintf(assembly, "lui   %s,   0x%04x", REG_NAME(op_dest->reg), op_src2->imm);
}

make_helper(ori) {

	decode_imm_type(instr);
	reg_w(op_dest->reg) = op_src1->val | op_src2->val;
	sprintf(assembly, "ori   %s,   %s,   0x%04x", REG_NAME(op_dest->reg), REG_NAME(op_src1->reg), op_src2->imm);
}

make_helper(addi){
	decode_imm_type(instr);
	int imm=op_src2->val; 
	int sign_src1=op_src1->val;
	imm=(imm<<16)>>16;
	int outcome=imm+sign_src1;
	int temp=sign_src1&0x80000000; int temp2=imm&0x80000000;
	if((temp==temp2)&&((outcome&0x80000000)!=temp)){
		//Exception
		if(cpu.cp0.status.EXL==0){
			cpu.cp0.cause.ExcCode=Ov;
			cpu.cp0.epc=cpu.pc;
			cpu.pc=Trap_addr;
			cpu.cp0.status.EXL=1;
		}
		// reg_w(op_dest->reg)=outcome;
	}else {
		reg_w(op_dest->reg)=outcome;
	}
	sprintf(assembly, "addi   %s,   %s,   0x%04x", REG_NAME(op_dest->reg), REG_NAME(op_src1->reg), op_src2->imm);
}

make_helper(addiu){
	decode_imm_type(instr);
	int imm=op_src2->val;
	imm=(imm<<16)>>16;
	reg_w(op_dest->reg)=imm+op_src1->val;
	sprintf(assembly, "addiu   %s,   %s,   0x%04x", REG_NAME(op_dest->reg), REG_NAME(op_src1->reg), op_src2->imm);
}

make_helper(slti){
	decode_imm_type(instr);
	int temp=op_src1->val ;
	int imm=(op_src2->val);
	imm=(imm<<16)>>16;
	reg_w(op_dest->reg)= temp < imm ? 1 : 0;
	sprintf(assembly, "slti   %s,   %s,   0x%04x", REG_NAME(op_dest->reg), REG_NAME(op_src1->reg), op_src2->imm);
}

make_helper(sltiu){
	decode_imm_type(instr);
	int imm=(op_src2->val);
	imm=(imm<<16)>>16;
	reg_w(op_dest->reg)=(op_src1->val < ((uint32_t)imm)) ? 1 : 0; 
	sprintf(assembly, "sltiu   %s,   %s,   0x%04x", REG_NAME(op_dest->reg), REG_NAME(op_src1->reg), op_src2->imm);
}

make_helper(andi){
	decode_imm_type(instr);
	int temp=0x0000ffff&op_src2->val;
	reg_w(op_dest->reg)=op_src1->val&temp;
	sprintf(assembly, "andi   %s,   %s,   0x%04x", REG_NAME(op_dest->reg), REG_NAME(op_src1->reg), op_src2->imm);
}

make_helper(xori){
	decode_imm_type(instr);
	int temp=0x0000ffff&op_src2->val;
	reg_w(op_dest->reg)=op_src1->val^temp;
	sprintf(assembly, "xori   %s,   %s,   0x%04x", REG_NAME(op_dest->reg), REG_NAME(op_src1->reg), op_src2->imm);
}

make_helper(bne){
	decode_imm_type(instr);
	op_dest->val=reg_w(op_dest->reg);
	if(op_src1->val!=op_dest->val){
		sprintf(assembly,"%x, %x\n",op_src1->val, op_dest->val);
		int target_offset=op_src2->val;
		target_offset=(target_offset<<16)>>14;
		uint32_t address=(int)cpu.pc+target_offset;
		cpu.pc=address;
	}
	sprintf(assembly, "bne   %s,   %s,   0x%04x", REG_NAME(op_dest->reg), REG_NAME(op_src1->reg), op_src2->val);
}

// Memory access INST
make_helper(lb)
{
	decode_imm_type(instr);
	int temp=op_src2->val; 
	temp=(temp<<16)>>16;
	int temp2=op_src1->val+temp;
	uint32_t addr=temp2;
	int outcome=mem_read(addr,1);
	outcome=(outcome<<24)>>24;
	reg_w(op_dest->reg)=outcome;
	sprintf(assembly, "lb   %s,  0x%04x(%s)", REG_NAME(op_dest->reg),  op_src2->val, REG_NAME(op_src1->reg));
}

make_helper(lbu)
{
	decode_imm_type(instr);
	int temp=op_src2->val; 
	temp=(temp<<16)>>16;
	int base=op_src1->val;
	int temp2=base+temp;
	uint32_t addr=temp2;
	uint32_t outcome=mem_read(addr,1);
	// outcome=(outcome<<24)>>24;
	reg_w(op_dest->reg)=outcome;
	sprintf(assembly, "lbu   %s,  0x%04x(%s)", REG_NAME(op_dest->reg),  op_src2->val, REG_NAME(op_src1->reg));
}

make_helper(lh)
{
	decode_imm_type(instr);
	int offset=op_src2->val;
	offset=(offset<<16)>>16;
	int base=op_src1->val;
	int addr=base+offset;
	if(addr&0x1){
		// THERE WILL TRIG ADDR_NOT_ALIGNMENT_EXCEPTION
		if(cpu.cp0.status.EXL==0){
			cpu.cp0.cause.ExcCode=AdEL;
			cpu.cp0.epc=cpu.pc;
			cpu.pc=Trap_addr;
			cpu.cp0.status.EXL=1;
		}
	}else{
		int outcome= mem_read((uint32_t)(addr), 2);
		outcome=(outcome<<16)>>16;
		reg_w(op_dest->reg)=outcome;
	}
	sprintf(assembly, "lh   %s,  0x%04x(%s)", REG_NAME(op_dest->reg),  op_src2->val, REG_NAME(op_src1->reg));
}

make_helper(lhu)
{
	decode_imm_type(instr);
	int offset=op_src2->val;
	offset=(offset<<16)>>16;
	int base=op_src1->val;
	int addr=base+offset;
	if(addr&0x1){
		// THERE WILL TRIG ADDR_NOT_ALIGNMENT_EXCEPTION
		if(cpu.cp0.status.EXL==0){
			cpu.cp0.cause.ExcCode=AdEL;
			cpu.cp0.epc=cpu.pc;
			cpu.pc=Trap_addr;
			cpu.cp0.status.EXL=1;
		}
	}else{
		uint32_t outcome= mem_read((uint32_t)(addr), 2);
		// outcome=(outcome<<16)>>16;
		reg_w(op_dest->reg)=outcome;
	}
	sprintf(assembly, "lhu   %s,  0x%04x(%s)", REG_NAME(op_dest->reg),  op_src2->val, REG_NAME(op_src1->reg));
}

make_helper(lw)
{
	decode_imm_type(instr);
	int offset=op_src2->val;
	offset=(offset<<16)>>16;
	int base=op_src1->val;
	int addr=base+offset;
	if(addr&0x3){
		// THERE WILL TRIG ADDR_NOT_ALIGNMENT_EXCEPTION
		if(cpu.cp0.status.EXL==0){
			cpu.cp0.cause.ExcCode=AdEL;
			cpu.cp0.epc=cpu.pc;
			cpu.pc=Trap_addr;
			cpu.cp0.status.EXL=1;
		}
	}else{
		int outcome= mem_read((uint32_t)(addr), 4);
		// outcome=(outcome<<16)>>16;
		reg_w(op_dest->reg)=outcome;
	}
	sprintf(assembly, "lw  %s,  0x%04x(%s)", REG_NAME(op_dest->reg),  op_src2->val, REG_NAME(op_src1->reg));
}

make_helper(sb){
	decode_imm_type(instr);
	int offset=op_src2->val;
	offset=(offset<<16)>>16;
	int base=op_src1->val;
	int addr=base+offset;
	mem_write((uint32_t) addr, 1, reg_w(op_dest->reg));
	sprintf(assembly, "sb %s, 0x%08x(%s)", REG_NAME(op_dest->reg), op_src2->val, REG_NAME(op_src1->reg));
}

make_helper(sh){
	decode_imm_type(instr);
	int offset=op_src2->val;
	offset=(offset<<16)>>16;
	int base=op_src1->val;
	int addr=base+offset;
	if(addr&0x1){
		if(cpu.cp0.status.EXL==0){
			sprintf(assembly,"exception");
			cpu.cp0.cause.ExcCode=AdES;
			cpu.cp0.epc=cpu.pc;
			cpu.pc=Trap_addr;
			cpu.cp0.status.EXL=1;
		}
	}else{
			mem_write((uint32_t) addr, 2, reg_w(op_dest->reg));
	}
	sprintf(assembly, "sb %s, 0x%08x(%s)", REG_NAME(op_dest->reg), op_src2->val, REG_NAME(op_src1->reg));
}

make_helper(sw){
	decode_imm_type(instr);
	int offset=op_src2->val;
	offset=(offset<<16)>>16;
	int base=op_src1->val;
	int addr=base+offset;
	if(addr&0x3){
		if(cpu.cp0.status.EXL==0){
			cpu.cp0.cause.ExcCode=AdES;
			cpu.cp0.epc=cpu.pc;
			cpu.pc=Trap_addr;
			cpu.cp0.status.EXL=1;
		}
	}else{
			mem_write((uint32_t) addr, 4, reg_w(op_dest->reg));
	}
	sprintf(assembly, "sw %s, 0x%08x(%s)", REG_NAME(op_dest->reg), op_src2->val, REG_NAME(op_src1->reg));
}


make_helper(beq){
	decode_imm_type(instr);
	if(reg_w(op_dest->reg) == reg_w(op_src1->reg)) {
		int temp;
		if(op_src2->val & 0x8000) {
			temp = (0xFFFF << 16) | op_src2->val;
		}else {
			temp = op_src2->val;
		}
		uint32_t addr = (int)cpu.pc + (temp << 2);
		cpu.pc = addr;
	}
	sprintf(assembly, "beq %s, %s, 0x%04x", REG_NAME(op_src1->reg), REG_NAME(op_dest->reg), op_src2->val);
}


make_helper(bgez) {
	decode_imm_type(instr);
	if((int)op_src1->val >= 0) {
		int temp;
		if(op_src2->val & 0x8000) {
			temp = (0xFFFF << 16) | op_src2->val;
		}else {
			temp = op_src2->val;
		}
		uint32_t addr = (int)cpu.pc + (temp << 2);
		cpu.pc = addr;
	}
	sprintf(assembly, "bgez %s, 0x%04x", REG_NAME(op_src1->reg), op_src2->val);
}

make_helper(bgtz) {
	decode_imm_type(instr);
	if((int)op_src1->val > 0) {
		int temp;
		if(op_src2->val & 0x8000) {
			temp = (0xFFFF << 16) | op_src2->val;
		}else {
			temp = op_src2->val;
		}
		uint32_t addr = (int)cpu.pc + (temp << 2);
		cpu.pc = addr;
	}
	sprintf(assembly, "bgtz %s, 0x%04x", REG_NAME(op_src1->reg), op_src2->val);
}

make_helper(blez) {
	decode_imm_type(instr);
	if((int)op_src1->val <= 0) {
		int temp;
		if(op_src2->val & 0x8000) {
			temp = (0xFFFF << 16) | op_src2->val;
		}else {
			temp = op_src2->val;
		}
		uint32_t addr = (int)cpu.pc + (temp << 2);
		cpu.pc = addr;
	}
	sprintf(assembly, "blez %s, 0x%04x", REG_NAME(op_src1->reg), op_src2->val);
}

make_helper(bltz) {
	decode_imm_type(instr);
	if((int)op_src1->val < 0) {
		int temp;
		if(op_src2->val & 0x8000) {
			temp = (0xFFFF << 16) | op_src2->val;
		}else {
			temp = op_src2->val;
		}
		uint32_t addr;
		if(temp < 0) {
			uint32_t a = (uint32_t)(~temp + 1);
			addr = cpu.pc - (a << 2);
		}else {
			addr = cpu.pc + (temp << 2);
		}
		cpu.pc = addr;
	}
	sprintf(assembly, "bltz %s, 0x%04x", REG_NAME(op_src1->reg), op_src2->val);
}

make_helper(bgezal) {
	decode_imm_type(instr);
	if(((int)op_src1->val) >= 0) {
		int temp;
		if(op_src2->val & 0x8000) {
			temp = (0xFFFF << 16) | op_src2->val;
		}else {
			temp = op_src2->val;
		}
		uint32_t addr = cpu.pc + (temp << 2);
		reg_w(31) = cpu.pc + 8;
		cpu.pc = addr;
	}else {
		reg_w(31) = cpu.pc + 8;
	}
	sprintf(assembly, "bgezal %s, 0x%04x", REG_NAME(op_src1->reg), op_src2->val);
}

make_helper(bltzal) {
	decode_imm_type(instr);
    if(((int)op_src1->val) < 0) {
        int temp;
        if(op_src2->val & 0x8000) {
            temp = (0xFFFF << 16) | op_src2->val;
        }else {
            temp = op_src2->val;
        }
        uint32_t addr = cpu.pc + (temp << 2);
        reg_w(31) = cpu.pc + 8;
        cpu.pc = addr;
    }else {
        reg_w(31) = cpu.pc + 8;
    }
	sprintf(assembly, "bltzal %s, 0x%04x", REG_NAME(op_src1->reg), op_src2->val);
}
